package com.algernoon.nettydemo.nio.client;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

public class TimeClientHandler implements Runnable {

	private String host;
	
	private int port;
	
	private Selector selector;
	
	private SocketChannel socketChannel;
	
	private volatile boolean stop;
	
	/**
	 * 初始化多路复用器和SocketChannel，将其设置为异步非阻塞
	 * 
	 * @param host
	 * @param port
	 */
	public TimeClientHandler(String host, int port) {
		this.host = host == null ? "127.0.0.1" : host;
		this.port = port;
		try {
			selector = Selector.open();
			socketChannel = SocketChannel.open();
			socketChannel.configureBlocking(Boolean.FALSE);
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	@Override
	public void run() {
		try {
			// 发送连接请求
			doConnect();
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		
		while(!stop) {
			try {
				// 轮询多路复用器，当有Channel就绪时执行handleInput(key)方法
				selector.select(1000);
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						handleInput(key);
					} catch (Exception e) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null) {
								key.channel().close();
							}
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				System.exit(1);
			}
		}
		
		// 多路复用器关闭后，所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭，所以不需要重复释放资源
		if (selector != null) {
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
	}
	
	private void doConnect() throws IOException {
		// 如果直接连接成功，则注册到多路复用器上，发送请求消息，读应答
		if (socketChannel.connect(new InetSocketAddress(host, port))) {
			socketChannel.register(selector, SelectionKey.OP_READ);
			doWrite(socketChannel);
		} else {
			// 没有连接成功说明服务端没有返回TCP握手应答消息，这并不代表连接失败，我们需要将socketChannel注册到多路复用器selector上
			// 注册SelectionKey.OP_CONNECT，当服务端返回syn-ack消息后，selector就能轮询到这个socketChannel处于连接就绪状态。
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
		}
	}
	
	private void handleInput(SelectionKey key) throws IOException {
		if (key.isValid()) {
			// 判断连接是否成功
			SocketChannel sc = (SocketChannel) key.channel();
			if (key.isConnectable()) { // 如果处于连接状态，说明服务端已经返回ACK应答信息
				if (sc.finishConnect()) { // 返回true,表示客户端连接成功
					// 将SocketChannel注册到多路复用器上，注册SelectionKey.OP_READ操作位，监听网络读操作
					sc.register(selector, SelectionKey.OP_READ);
					// 发送消息给服务端
					doWrite(sc);
				} else {
					// 连接失败，进程退出
					System.exit(1);
				}
			}
			
			// 处理服务器应答消息
			if (key.isReadable()) {
				// 如果客户端接收到服务端的应答消息，则SocketChannel是可读的，由于实现无法判断应答码流的大小，就预分配1M的接收缓存区用于读取应答消息
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				// 异步读取操作
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) { // 读取到就解码打印结果
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, Charset.forName("UTF-8"));
					System.out.println("Now Time Is " + body);
					// 执行完成后stop值为true，线程退出循环
					this.stop = Boolean.TRUE;
				} else if(readBytes < 0) {
					// 对端链路关闭
					key.cancel();
					sc.close();
				} else {
					
				}
			}
		}
	}

	private void doWrite(SocketChannel sc) throws IOException {
		byte[] req = "QUERY_TIME_ORDER".getBytes();
		ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
		writeBuffer.put(req);
		writeBuffer.flip();
		sc.write(writeBuffer);
		if (!writeBuffer.hasRemaining()) {
			System.out.println("send request to time server successed.");
		}
	}

}
